package com.jiaqiang.chatgpt.session.defaults;

import cn.hutool.core.util.StrUtil;
import cn.hutool.http.ContentType;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.jiaqiang.chatgpt.domain.billing.BillingUsage;
import com.jiaqiang.chatgpt.domain.billing.CreditSummary;
import com.jiaqiang.chatgpt.domain.billing.Subscription;
import com.jiaqiang.chatgpt.domain.chat.ChatCompletionRequest;
import com.jiaqiang.chatgpt.domain.chat.ChatCompletionResponse;
import com.jiaqiang.chatgpt.domain.edits.EditRequest;
import com.jiaqiang.chatgpt.domain.edits.EditResponse;
import com.jiaqiang.chatgpt.domain.embedd.EmbeddingRequest;
import com.jiaqiang.chatgpt.domain.embedd.EmbeddingResponse;
import com.jiaqiang.chatgpt.domain.files.DeleteFileResponse;
import com.jiaqiang.chatgpt.domain.files.FileListResponse;
import com.jiaqiang.chatgpt.domain.files.UploadFileResponseListResponse;
import com.jiaqiang.chatgpt.domain.images.ImageEditRequest;
import com.jiaqiang.chatgpt.domain.images.ImageRequest;
import com.jiaqiang.chatgpt.domain.images.ImageResponse;
import com.jiaqiang.chatgpt.domain.other.OpenAiResponse;
import com.jiaqiang.chatgpt.domain.qa.QACompletionRequest;
import com.jiaqiang.chatgpt.domain.qa.QACompletionResponse;
import com.jiaqiang.chatgpt.IOpenAiApi;
import com.jiaqiang.chatgpt.domain.whisper.TranscriptionsRequest;
import com.jiaqiang.chatgpt.domain.whisper.TranslationsRequest;
import com.jiaqiang.chatgpt.domain.whisper.WhisperResponse;
import com.jiaqiang.chatgpt.session.Configuration;
import com.jiaqiang.chatgpt.session.OpenAiSession;
import io.reactivex.Single;
import lombok.extern.slf4j.Slf4j;
import okhttp3.MediaType;
import okhttp3.MultipartBody;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.sse.EventSource;
import okhttp3.sse.EventSourceListener;
import org.jetbrains.annotations.NotNull;

import java.io.File;
import java.time.LocalDate;
import java.util.*;

@Slf4j
public class DefaultOpenAiSession implements OpenAiSession {

    /** 配置信息 */
    private final Configuration configuration;
    /** 工厂事件 */
    private final EventSource.Factory factory;
    /** openai 接口 */
    private final IOpenAiApi openAiApi;

    public DefaultOpenAiSession(Configuration configuration) {
        this.configuration = configuration;
        this.openAiApi = configuration.getOpenAiApi();
        this.factory = configuration.createRequestFactory();
    }

    /**
     * 文本问答；简单请求
     * @param question 请求信息
     * @return         返回结果
     */
    @Override
    public QACompletionResponse chatCompletions(String question) {
        QACompletionRequest request = QACompletionRequest
                .builder()
                .prompt(question)
                .build();
        Single<QACompletionResponse> completions = this.openAiApi.completions(request);
        return completions.blockingGet();
    }

    /**
     * 文本问答
     * @param qaCompletionRequest 请求信息
     * @return                    返回结果
     */
    @Override
    public QACompletionResponse chatCompletions(QACompletionRequest qaCompletionRequest) {
        return this.openAiApi.completions(qaCompletionRequest).blockingGet();
    }

    /**
     * 流式回答
     * @param qaCompletionRequest 请求信息
     * @param eventSourceListener 实现监听, 通过监听 onEvent 方法接收数据
     * @return                    返回结果
     */
    @Override
    public EventSource chatCompletions(QACompletionRequest qaCompletionRequest, EventSourceListener eventSourceListener) throws JsonProcessingException {
        if (!qaCompletionRequest.isStream()) {
            // 必传参数, .stream(true)
            throw new RuntimeException("illegal stream is false, but it must be true.");
        }
        // 构建请求信息
        String requestBody = new ObjectMapper().writeValueAsString(qaCompletionRequest);
        RequestBody body = RequestBody.create(MediaType.parse(ContentType.JSON.getValue()), requestBody);
        Request request = new Request.Builder()
                // "https://api.openai.com/v1/completions"
                .url(configuration.getApiHost().concat(IOpenAiApi.v1_completions))
                .post(body)
                .build();
        // 返回应答(事件结果)
        return factory.newEventSource(request, eventSourceListener);
    }


    /**
     * 默认 GPT-3.5 问答模型
     * @param chatCompletionRequest 请求信息
     * @return                      返回结果
     */
    @Override
    public ChatCompletionResponse chatCompletions(ChatCompletionRequest chatCompletionRequest) {
        return this.openAiApi.completions(chatCompletionRequest).blockingGet();
    }


    /**
     * 默认 GPT-3.5 问答模型 流式回答
     * @param chatCompletionRequest 请求信息
     * @param eventSourceListener 实现监听, 通过监听 onEvent 方法接收数据
     * @return                    返回结果
     */
    @Override
    public EventSource chatCompletions(ChatCompletionRequest chatCompletionRequest, EventSourceListener eventSourceListener) throws JsonProcessingException {
        if (!chatCompletionRequest.isStream()) {
            // 必传参数, .stream(true)
            throw new RuntimeException("illegal stream is false, but it must be true.");
        }
        // 构建请求信息
        String requestBody = new ObjectMapper().writeValueAsString(chatCompletionRequest);
        RequestBody body = RequestBody.create(MediaType.parse(ContentType.JSON.getValue()), requestBody);
        Request request = new Request.Builder()
                // "https://api.openai.com/v1/chat/completions"
                .url(configuration.getApiHost().concat(IOpenAiApi.v1_chat_completions))
                .post(body)
                .build();
        // 返回应答(事件结果)
        return factory.newEventSource(request, eventSourceListener);
    }


    /**
     * 文本修复
     *
     * @param editRequest 请求信息
     * @return 应答结果
     */
    @Override
    public EditResponse edit(EditRequest editRequest) {
        return this.openAiApi.edits(editRequest).blockingGet();
    }

    /**
     * 简单的生成图片
     *
     * @param prompt 图片描述
     * @return 应答结果
     */
    @Override
    public ImageResponse genImages(String prompt) {
        ImageRequest imageRequest = ImageRequest.builder().prompt(prompt).build();
        log.info(imageRequest.toString());
        return this.genImages(imageRequest);
    }

    /**
     * 生成图片
     *
     * @param imageRequest 图片描述
     * @return 应答结果
     */
    @Override
    public ImageResponse genImages(ImageRequest imageRequest) {
        return this.openAiApi.genImagesGenerations(imageRequest).blockingGet();
    }

    /**
     * 修改图片
     *
     * @param image  图片对象
     * @param prompt 修改描述
     * @return 应答结果
     */
    @Override
    public ImageResponse editImages(File image, String prompt) {
        ImageEditRequest imageEditRequest = ImageEditRequest.builder().prompt(prompt).build();
        return this.editImages(image, null, imageEditRequest);
    }

    /**
     * 修改图片
     *
     * @param image            图片对象
     * @param imageEditRequest 图片参数
     * @return 应答结果
     */
    @Override
    public ImageResponse editImages(File image, ImageEditRequest imageEditRequest) {
        return this.editImages(image, null, imageEditRequest);
    }

    /**
     * 修改图片
     *
     * @param image            图片对象，小于4M的PNG图片
     * @param mask             图片对象，小于4M的PNG图片
     * @param imageEditRequest 图片参数
     * @return 应答结果
     */
    @Override
    public ImageResponse editImages(File image, File mask, ImageEditRequest imageEditRequest) {
        // 1. imageMultipartBody
        RequestBody imageBody = RequestBody.create(MediaType.parse("multipart/form-data"), image);
        MultipartBody.Part imageMultipartBody = MultipartBody.Part.createFormData("image", image.getName(), imageBody);
        // 2. maskMultipartBody
        MultipartBody.Part maskMultipartBody = null;
        if (Objects.nonNull(mask)) {
            RequestBody maskBody = RequestBody.create(MediaType.parse("multipart/form-data"), mask);
            maskMultipartBody = MultipartBody.Part.createFormData("mask", mask.getName(), maskBody);
        }
        // requestBodyMap
        Map<String, RequestBody> requestBodyMap = new HashMap<>();
        requestBodyMap.put("prompt", RequestBody.create(MediaType.parse("multipart/form-data"), imageEditRequest.getPrompt()));
        requestBodyMap.put("n", RequestBody.create(MediaType.parse("multipart/form-data"), imageEditRequest.getN().toString()));
        requestBodyMap.put("size", RequestBody.create(MediaType.parse("multipart/form-data"), imageEditRequest.getSize()));
        requestBodyMap.put("response_format", RequestBody.create(MediaType.parse("multipart/form-data"), imageEditRequest.getResponseFormat()));
        if (!(Objects.isNull(imageEditRequest.getUser()) || "".equals(imageEditRequest.getUser()))) {
            requestBodyMap.put("user", RequestBody.create(MediaType.parse("multipart/form-data"), imageEditRequest.getUser()));
        }
        return this.openAiApi.editImages(imageMultipartBody, maskMultipartBody, requestBodyMap).blockingGet();
    }

    /**
     * 向量计算；单个文本
     * 文本向量计算是一种在自然语言处理（NLP）领域中用于测量和比较文本相似性的技术。在这种方法中，
     * 每个单词或短语都被转换为一个向量，可以使用这些向量来比较不同文本之间的相似性，并在需要时进行推荐或分类
     *
     * @param input 文本信息
     * @return 应答结果
     */
    @Override
    public EmbeddingResponse embeddings(String input) {
        EmbeddingRequest embeddingRequest = EmbeddingRequest
                .builder()
                .input(new ArrayList<String>() {{
                    add(input);
                }}).build();
        return this.embeddings(embeddingRequest);
    }

    /**
     * 向量计算；多个文本
     * 文本向量计算是一种在自然语言处理（NLP）领域中用于测量和比较文本相似性的技术。在这种方法中，每个单词或短语都被转换为一个向量，可以使用这些向量来比较不同文本之间的相似性，并在需要时进行推荐或分类
     *
     * @param inputs 多个文本
     * @return 应答结果
     */
    @Override
    public EmbeddingResponse embeddings(String... inputs) {
        EmbeddingRequest embeddingRequest = EmbeddingRequest.builder().input(Arrays.asList(inputs)).build();
        return this.embeddings(embeddingRequest);
    }

    /**
     * 向量计算；多个文本
     * 文本向量计算是一种在自然语言处理（NLP）领域中用于测量和比较文本相似性的技术。在这种方法中，
     * 每个单词或短语都被转换为一个向量，可以使用这些向量来比较不同文本之间的相似性，并在需要时进行推荐或分类
     *
     * @param inputs 多个文本
     * @return 应答结果
     */
    @Override
    public EmbeddingResponse embeddings(List<String> inputs) {
        EmbeddingRequest embeddingRequest = EmbeddingRequest.builder().input(inputs).build();
        return this.embeddings(embeddingRequest);
    }

    /**
     * 向量计算；入参
     * 文本向量计算是一种在自然语言处理（NLP）领域中用于测量和比较文本相似性的技术。在这种方法中，每个单词或短语都被转换为一个向量，可以使用这些向量来比较不同文本之间的相似性，并在需要时进行推荐或分类
     *
     * @param embeddingRequest 请求结果
     * @return 应答结果
     */
    @Override
    public EmbeddingResponse embeddings(EmbeddingRequest embeddingRequest) {
        return this.openAiApi.embeddings(embeddingRequest).blockingGet();
    }

    /**
     * 获取文件
     *
     * @return 应答结果
     */
    @Override
    public OpenAiResponse<FileListResponse> files() {
        return this.openAiApi.files().blockingGet();
    }

    /**
     * 上传文件
     *
     * @param file 文件
     * @return 应答结果
     */
    @Override
    public UploadFileResponseListResponse uploadFile(File file) {
        return this.uploadFile("user_data", file);
    }

    /**
     * 上传文件
     *
     * @param purpose Use "fine-tune" for Fine-tuning. This allows us to validate the format of the uploaded file.
     *                使用 user_data, fine-tune 会报错
     * @param file    文件
     * @return 应答结果
     */
    @Override
    public UploadFileResponseListResponse uploadFile(String purpose, File file) {
        RequestBody fileBody = RequestBody.create(MediaType.parse("multipart/form-data"), file);
        MultipartBody.Part multipartBody = MultipartBody.Part.createFormData("file", file.getName(), fileBody);
        if (purpose == null || "".equals(purpose)) {
            purpose = UploadFileResponseListResponse.Purpose.USER_DATA.getCode();
        }
        RequestBody purposeBody = RequestBody.create(MediaType.parse("multipart/form-data"), purpose);
        return this.openAiApi.uploadFile(multipartBody, purposeBody).blockingGet();
    }

    /**
     * 删除文件
     *
     * @param fileId 文件ID
     * @return 应答结果
     */
    @Override
    public DeleteFileResponse deleteFile(String fileId) {
        return this.openAiApi.deleteFile(fileId).blockingGet();
    }

    /**
     * 语音转文字
     *
     * @param file                  语音文件
     * @param transcriptionsRequest 请求信息
     * @return 应答结果
     */
    @Override
    public WhisperResponse speed2TextTranscriptions(File file, TranscriptionsRequest transcriptionsRequest) {
        // 1. 语音文件
        RequestBody fileBody = RequestBody.create(MediaType.parse("multipart/form-data"), file);
        MultipartBody.Part multipartBody = MultipartBody.Part.createFormData("file", file.getName(), fileBody);
        // 2. 参数封装
        Map<String, RequestBody> requestBodyMap = new HashMap<>();
        if (StrUtil.isNotBlank(transcriptionsRequest.getLanguage())) {
            requestBodyMap.put(TranscriptionsRequest.Fields.language, RequestBody.create(MediaType.parse("multipart/form-data"), transcriptionsRequest.getLanguage()));
        }
        if (StrUtil.isNotBlank(transcriptionsRequest.getModel())) {
            requestBodyMap.put(TranscriptionsRequest.Fields.model, RequestBody.create(MediaType.parse("multipart/form-data"), transcriptionsRequest.getModel()));
        }
        if (StrUtil.isNotBlank(transcriptionsRequest.getPrompt())) {
            requestBodyMap.put(TranscriptionsRequest.Fields.prompt, RequestBody.create(MediaType.parse("multipart/form-data"), transcriptionsRequest.getPrompt()));
        }
        if (StrUtil.isNotBlank(transcriptionsRequest.getResponseFormat())) {
            requestBodyMap.put(TranscriptionsRequest.Fields.responseFormat, RequestBody.create(MediaType.parse("multipart/form-data"), transcriptionsRequest.getResponseFormat()));
        }
        requestBodyMap.put(TranscriptionsRequest.Fields.temperature, RequestBody.create(MediaType.parse("multipart/form-data"), String.valueOf(transcriptionsRequest.getTemperature())));
        return this.openAiApi.speed2TextTranscriptions(multipartBody, requestBodyMap).blockingGet();
    }

    /**
     * 语音翻译
     *
     * @param file                语音文件
     * @param translationsRequest 请求信息
     * @return 应答结果
     */
    @Override
    public WhisperResponse speed2TextTranslations(File file, TranslationsRequest translationsRequest) {
        // 1. 语音文件
        RequestBody fileBody = RequestBody.create(MediaType.parse("multipart/form-data"), file);
        MultipartBody.Part multipartBody = MultipartBody.Part.createFormData("file", file.getName(), fileBody);
        // 2. 参数封装
        Map<String, RequestBody> requestBodyMap = new HashMap<>();
        if (StrUtil.isNotBlank(translationsRequest.getModel())) {
            requestBodyMap.put(TranslationsRequest.Fields.model, RequestBody.create(MediaType.parse("multipart/form-data"), translationsRequest.getModel()));
        }
        if (StrUtil.isNotBlank(translationsRequest.getPrompt())) {
            requestBodyMap.put(TranslationsRequest.Fields.prompt, RequestBody.create(MediaType.parse("multipart/form-data"), translationsRequest.getPrompt()));
        }
        if (StrUtil.isNotBlank(translationsRequest.getResponseFormat())) {
            requestBodyMap.put(TranslationsRequest.Fields.responseFormat, RequestBody.create(MediaType.parse("multipart/form-data"), translationsRequest.getResponseFormat()));
        }
        requestBodyMap.put(TranslationsRequest.Fields.temperature, RequestBody.create(MediaType.parse("multipart/form-data"), String.valueOf(translationsRequest.getTemperature())));
        requestBodyMap.put(TranscriptionsRequest.Fields.temperature, RequestBody.create(MediaType.parse("multipart/form-data"), String.valueOf(translationsRequest.getTemperature())));
        return this.openAiApi.speed2TextTranscriptions(multipartBody, requestBodyMap).blockingGet();
    }

    /**
     * 账单查询
     *
     * @return 应答结果
     */
    @Override
    public Subscription subscription(String authorization) {
        return this.openAiApi.subscription(authorization).blockingGet();
    }

    /**
     * 消耗查询
     *
     * @param starDate 开始时间
     * @param endDate  结束时间
     * @return  应答数据
     */
    @Override
    public BillingUsage billingUsage(@NotNull LocalDate starDate, @NotNull LocalDate endDate) {
        return this.openAiApi.billingUsage(starDate, endDate).blockingGet();
    }

    /**
     * 余额查询 需要 session key
     * @return 应答数据
     */
    @Override
    public CreditSummary billingCreditGrants() {
        return  this.openAiApi.billingCreditGrants().blockingGet();
    }


}